/*
 * Simple OpenGL 3 Tutorial.
 *
 * Sample program 01.
 *
 */

#include <gl_app.h>
#include <gu_shader.h>

#include<GL/glew.h>

#include <gu_vertex_buffer.h>
#include <gu_util.h>
#include <gu_renderer.h>
#include <gu_render_mesh.h>
#include <gu_material_slot.h>
#include <gu_render_context.h>
#include <gu_material.h>

#include <gr_algorithms.h>
#include <gr_tokens.h>
#include <gr_env_types.h>

#include <cassert>
#include <memory> 
#include <map>

typedef std::unique_ptr<gu::Shader> ShaderPtr;
typedef std::unique_ptr<gu::VertexBuffer> VertexBufferPtr;
typedef std::unique_ptr<gu::IndexBuffer> IndexBufferPtr;
typedef std::unique_ptr<gu::Renderer> RendererPtr;
typedef std::unique_ptr<gu::RenderMesh> RenderMeshPtr;


ShaderPtr           shader;
VertexBufferPtr     vertexBuffer;
IndexBufferPtr      indexBuffer;
RendererPtr         renderer;
gu::MaterialSlotPtr materialSlot;

gu::state::DataSequence renderSettings;
gu::Material::shared_ptr material;


const char* windowTitle = "Program 01";

/* Our vertex shader. This is run for each vertex.*/

#define T "t"
#define VRAD "radialSpeed"

const GLchar* VertexShader =
        "#version 330\n"
        "in vec4 "GR_POSITION";\n"
        "in vec4 "GR_COLOR0";\n"
        "uniform float "T";\n"
        "uniform float "VRAD";\n"
        "out vec4 v_color;\n"
        "void main(void)\n"
        "{\n"
         "  float angle = "VRAD" * "T";\n"
         "  float s = sin(angle);\n"
         "  float c = cos(angle);\n"
        "   mat4 rotMatrix = mat4(c, s, 0 ,0,\n"
        "                 -s, c, 0, 0,\n"
        "                  0, 0, 1, 0,\n"
        "                  0, 0, 0,1);\n"
        "   gl_Position = rotMatrix * "GR_POSITION";\n"
        "   v_color = "GR_COLOR0";\n"
        "}\n";


/*Our fragment shader. Run for each pixel under triangle.*/
const GLchar* FragmentShader =
        "#version 330\n"
        "in vec4 v_color;\n"
        "out vec4 out_Color;\n"
        "void main(void)\n"
        "{\n"
        "       out_Color = v_color;\n"
        "}\n";

unsigned FrameCount = 0;


void createVBO(void)
{
/* position: 4 values; color: 4 values. Aligning to 16 or 32 byte boundaries is sometimes ok. */
    GLfloat vertices[] = {
                -0.8f, -0.8f, 0.0f, 1.0f,  1.0f, 0.0f, 0.0f, 1.0f,
                 0.0f,  0.8f, 0.0f, 1.0f,  0.0f, 1.0f, 0.0f, 1.0f,
                 0.8f, -0.8f, 0.0f, 1.0f,  0.0f, 0.0f, 1.0f, 1.0f
                
    };

    uint16_t indices[] = {0, 1, 2};
    uint16_t lineIndices[] = {0, 1, 1, 2, 2, 0};

    GLenum ErrorCheckValue = glGetError();
    
    /*
    *  What comes next is also an introduction to the general way in which
    *  internal objects to OpenGL are managed.
    */

    vertexBuffer.reset(gu::newGlVertexBuffer()); // part of resource management  ... render mesh?
    indexBuffer.reset(gu::newGlIndexBuffer());   // part of resource management  ... render mesh?

    // - do we need meshpropertymap
    gu::LocMap& streamLocs = material->inputStreamLocMap();

    // todo auto mapping from stream map to here
    gr::StreamSignature& signature(vertexBuffer->streamData().signature());
    signature.add(gr::STREAM_FLOAT4, GR_POSITION);
    signature.add(gr::STREAM_FLOAT4, GR_COLOR0);

    vertexBuffer->updateParameters(streamLocs); // reflect

    vertexBuffer->streamData().setData(reinterpret_cast<uint8_t*>(vertices), sizeof(vertices));
    indexBuffer->indexData().setData(indices, 3);

    // TODO sidecar for buffers. new buffer types (4 combinations total)
    // buffers: interleaved|side_by_side  indexed|unindexed
    // shader vertex and index buffers through shared_ptrs : can have instance for solid (faces) mesh and line mesh,
    // that both refer to same vertex buffer but have different index buffers
    // index or no concern is separate of organization of buffer data!
    vertexBuffer->init(); // part of resource management - todo, to renderMesh interface ?
    indexBuffer->init();  // part of resource management

    materialSlot->mapBuffers(vertexBuffer.get(), indexBuffer.get());
}

void createShaders(void)
{
    material.reset(gu::newGlMaterial(VertexShader, FragmentShader));
}

void renderFunction(float t)
{
        ++FrameCount;

        renderer->applyState(renderSettings);
        float radialSpeed = 0.001f;
        gu::UniformMap* umap = renderer->getShaderAdapter().uniformMap();

        auto uniforms = material->uniforms();
        uniforms[T]->set(t);
        uniforms[VRAD]->set(radialSpeed);

        material->use(umap);

        materialSlot->render(); // Part of scene graph? Remove materialSlot.
}

void appConfigure(GlApp::Configuration& config)
{
    config.window().name() = windowTitle;
}

void appInitialize(GlApp::AppState& state)
{
    renderer.reset(gu::newRenderer());
    materialSlot.reset(gu::newMaterialSlot(*renderer));

    createShaders();
    createVBO();

    using namespace gu::state;
    add(renderSettings, setClearColor(1.0f, 1.0f, 1.0f));
    add(renderSettings, clearColorBuffer());
    add(renderSettings, clearDepthBuffer());
}

void appUpdate(GlApp::AppState& state)
{
    float t = static_cast<float>(state.elapsedTimeMs());
    renderFunction(t);
}

void appFinalize(GlApp::AppState& state)
{
    // Done, nothing to do.
}

void appResize(GlApp::AppState& state, int width, int height)
{
}
